package apiserver

import (
	"encoding/base64"
	"net/http"
	"strings"
	"time"

	"cs0512/fmr/internal/apiserver/domain"
	"cs0512/fmr/internal/apiserver/store"
	"cs0512/fmr/internal/pkg/middleware"
	"cs0512/fmr/internal/pkg/middleware/auth"
	"cs0512/fmr/internal/pkg/response"
	"cs0512/fmr/pkg/model"

	jwt "github.com/appleboy/gin-jwt/v2"
	"github.com/gin-gonic/gin"
	"github.com/marmotedu/log"
	"github.com/spf13/viper"
)

const (
	APIServerAudience = "fmr.cs0512.top"
	APIServerIssuer   = "fmr-apiserver"
)

type loginInfo struct {
	Username string `form:"username" json:"username" binding:"required,username"`
	Password string `form:"password" json:"password" binding:"required,password"`
}

// func newBasicAuth() middleware.AuthStrategy {
// 	return auth.NewBasicStrategy(func(username string, password string) bool {
// 		//验证用户信息
// 		return true
// 	})
// }

func newJWTAuth() middleware.AuthStrategy {
	ginjwt, _ := jwt.New(&jwt.GinJWTMiddleware{
		Realm:            viper.GetString("jwt.Realm"),
		SigningAlgorithm: "HS256",
		Key:              []byte(viper.GetString("jwt.signing-key")),
		//Timeout:          viper.GetDuration("jwt.buffer-time"),
		//MaxRefresh:       viper.GetDuration("jwt.max-refresh"),
		Timeout:         14400 * time.Second,
		MaxRefresh:      14400 * time.Second,
		Authenticator:   authenticator(),
		LoginResponse:   loginResponse(),
		LogoutResponse:  logoutResponse(),
		RefreshResponse: refreshResponse(),
		PayloadFunc:     payloadFunc(),
		IdentityHandler: func(c *gin.Context) interface{} {
			claims := jwt.ExtractClaims(c)
			return claims[middleware.UsernameKey]
		},
		IdentityKey:   middleware.UsernameKey,
		Authorizator:  authorizator(),
		Unauthorized:  unauthorized(),
		TokenLookup:   "header: Authorization, query: token, cookie: jwt",
		TokenHeadName: "Bearer",
		SendCookie:    true,
		TimeFunc:      time.Now,
	})

	return auth.NewJWTStrategy(*ginjwt)
}

// func newAutoAuth() middleware.AuthStrategy {
// 	return auth.NewAutoStrategy(newBasicAuth().(auth.BasicStrategy), newJWTAuth().(auth.JWTStrategy), newTokenAuth().(auth.TokenStrategy))
// }

func authenticator() func(c *gin.Context) (interface{}, error) {
	return func(c *gin.Context) (interface{}, error) {
		var login loginInfo
		var err error

		if c.Request.Header.Get("Authorization") != "" {
			login, err = parseWithHeader(c)
		} else {
			login, err = parseWithBody(c)
		}

		if err != nil {
			return "", jwt.ErrFailedAuthentication
		}
		user, err := domain.NewService(store.Client()).User().CheckLogin(c, login.Username, login.Password)
		//返回值中需要返回model.User,通过context传递
		if err == nil {
			c.Set("user", user)
		}
		return user, err
	}
}

func unauthorized() func(c *gin.Context, code int, message string) {
	return func(c *gin.Context, code int, message string) {
		response.Fail(c, 1000, message)
	}
}

func parseWithHeader(c *gin.Context) (loginInfo, error) {
	auth := strings.SplitN(c.Request.Header.Get("Authorization"), " ", 2)
	if len(auth) != 2 || auth[0] != "Basic" {
		log.Errorf("get basic string from Authorization header failed")

		return loginInfo{}, jwt.ErrFailedAuthentication
	}

	payload, err := base64.StdEncoding.DecodeString(auth[1])
	if err != nil {
		log.Errorf("decode basic string: %s", err.Error())

		return loginInfo{}, jwt.ErrFailedAuthentication
	}

	pair := strings.SplitN(string(payload), ":", 2)
	if len(pair) != 2 {
		log.Errorf("parse payload failed")

		return loginInfo{}, jwt.ErrFailedAuthentication
	}

	return loginInfo{
		Username: pair[0],
		Password: pair[1],
	}, nil
}

func parseWithBody(c *gin.Context) (loginInfo, error) {
	var login loginInfo
	var d map[string]string = map[string]string{"auth": ""}
	if err := c.ShouldBindJSON(&d); err != nil {
		log.Errorf("parse login parameters: %s", err.Error())

		return loginInfo{}, jwt.ErrFailedAuthentication
	}
	playload, err := base64.StdEncoding.DecodeString(d["auth"])
	if err != nil {
		log.Errorf("parse login parameters:%s", err.Error())
		return loginInfo{}, jwt.ErrFailedAuthentication
	}
	pair := strings.Split(string(playload), ":")
	if len(pair) < 2 {
		return loginInfo{}, jwt.ErrFailedAuthentication
	}
	login.Username = pair[0]
	login.Password = pair[1]

	return login, nil
}

func refreshResponse() func(c *gin.Context, code int, token string, expire time.Time) {
	return func(c *gin.Context, code int, token string, expire time.Time) {
		c.JSON(http.StatusOK, gin.H{
			"token":  token,
			"expire": expire.Format(time.RFC3339),
		})
	}
}

func loginResponse() func(c *gin.Context, code int, token string, expire time.Time) {
	return func(c *gin.Context, code int, token string, expire time.Time) {
		user, _ := c.Get("user")
		data := make(map[string]interface{})
		data["user"] = user
		data["token"] = token
		data["expire"] = expire.Format(time.RFC3339)
		response.OK(c, data, "")
	}
}

func logoutResponse() func(c *gin.Context, code int) {
	return func(c *gin.Context, code int) {
		response.OK(c, nil, "")
	}
}

func payloadFunc() func(data interface{}) jwt.MapClaims {
	return func(data interface{}) jwt.MapClaims {
		claims := jwt.MapClaims{
			"iss": APIServerIssuer,
			"aud": APIServerAudience,
		}
		if u, ok := data.(*model.User); ok {
			claims[middleware.UsernameKey] = u.ID
		}

		return claims
	}
}

func authorizator() func(data interface{}, c *gin.Context) bool {
	return func(data interface{}, c *gin.Context) bool {
		uid, ok := data.(float64)
		if !ok {
			log.Infof("user `%d` is not authenticated.", uid)
			return false
		}

		user, err := domain.NewService(store.Client()).User().GetUserByUid(c, int(uid))
		if err != nil {
			log.Infof("user with id %d is not found.", uid)
			return false
		}

		if user.Status == 0 {
			log.Infof("user %s is closed.", user.Username)
			return false
		}

		c.Set("user", user)

		return true
	}
}

// new token auth
// func newTokenAuth() middleware.AuthStrategy {
// 	return auth.NewTokenStrategy()
// }
